﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

namespace scanner_btc
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("btc addr scanner.");

            string type = args[0].ToLower();
            if (type == "btc")
            {
                uri = new Uri("http://127.0.0.1:22555");
                network = NBitcoin.Bitcoin.Instance.Mainnet;
            }
            else if (type == "ltc")
            {
                uri = new Uri("http://127.0.0.1:9332");
                network = NBitcoin.Altcoins.AltNetworkSets.Litecoin.Mainnet;
            }
            else if (type == "doge")
            {
                uri = new Uri("http://127.0.0.1:22555");
                network = NBitcoin.Altcoins.AltNetworkSets.Dogecoin.Mainnet;
            }
            Console.WriteLine("network=" + network.Name);


            System.Threading.ThreadPool.QueueUserWorkItem((s) =>
              {
                  System.Threading.Tasks.Task.WaitAll(ScanWork());
              });

            while (true)
            {
                Console.Write(">");
                var l = Console.ReadLine();
                if (l == "exit")
                {
                    return;
                }
            }

        }
        static uint GetCurCount()
        {
            if (System.IO.File.Exists(network.Name + "count.txt"))
            {
                var count = uint.Parse(System.IO.File.ReadAllText(network.Name + "count.txt"), System.Globalization.NumberStyles.HexNumber);
                return count;
            }
            return 0;
        }
        static void SetCurCount(uint count)
        {
            System.IO.File.WriteAllText(network.Name + "count.txt", count.ToString("X08"));
        }
        //原来nbitcoin已经内置了很多altchain的实现，只要在uri和network上动下手脚的事情
        static Uri uri;
        static NBitcoin.Network network;
        static async Task ScanWork()
        {
            var au = new NetworkCredential("li", "1");
            NBitcoin.RPC.RPCClient client = new NBitcoin.RPC.RPCClient(au, uri, network);
            Console.WriteLine("connect");
            Dictionary<string, bool> addrs = new Dictionary<string, bool>();
            List<string> lines = new List<string>();

            var serverCount = await client.GetBlockCountAsync();
            uint curCount = GetCurCount();
            Console.WriteLine("blockcount=" + curCount + "/" + serverCount);

            DateTime time = DateTime.Now;
            DateTime time2 = time;//每10s log一次
            while (true)
            {
                if (client == null)
                {
                    try
                    {
                        client = new NBitcoin.RPC.RPCClient(au, uri, network);
                        Console.WriteLine("reconnect");

                    }
                    catch
                    {
                        Console.WriteLine("reconnecterror");
                        client = null;
                    }
                }
                if (curCount < serverCount)
                {
                    try
                    {
                        await GetAddrOnBlock(client, curCount, addrs, lines);
                    }
                    catch (Exception err)
                    {
                        Console.WriteLine("got net error:" + err.Message);
                        try
                        {
                            client.Stop();
                        }
                        catch
                        {

                        }

                        client = null;
                        continue;
                    }
                    curCount++;

                    var perblockfile = 0x1000;//4k
                    //地址太多了，改4k一格吧 //4k存一格文件
                    if (curCount != 0 && curCount % perblockfile == 0)//6w清理一次地址缓存
                    {
                        //并刷一下高度
                        serverCount = await client.GetBlockCountAsync();

                        addrs.Clear();

                        Console.Clear();
                        Console.WriteLine("Finish " + curCount.ToString("X08"));
                    }
                    if (curCount % 0x200 == 0)//512 log 一次
                    {
                        //time log
                        DateTime time1 = DateTime.Now;
                        Console.WriteLine("blockcount=" + curCount + "/" + serverCount + " delta=" + (time1 - time).TotalSeconds);
                        time = time1;
                        time2 = time1;

                        //限制一下去重集的总大小，最大16M条记录
                        if (addrs.Count > 0x1000000)
                            addrs.Clear();
                        //addr log

                        var filename = "addrs_" + network.Name + "_" + ((curCount / perblockfile) * perblockfile).ToString("X08") + ".txt";
                        System.IO.File.AppendAllLines(filename, lines.ToArray());
                        lines.Clear();
                        //height  log
                        SetCurCount(curCount);
                    }
                    DateTime times = DateTime.Now;
                    if ((times - time2).TotalSeconds > 10)
                    {
                        time2 = times;
                        Console.WriteLine("10s " + curCount + "/" + serverCount);
                    }
                }
                else
                {
                    //30秒刷新高度
                    DateTime time1 = DateTime.Now;
                    if ((time1 - time).TotalSeconds > 30)
                    {
                        serverCount = await client.GetBlockCountAsync();
                        time = time1;
                    }
                    System.Threading.Thread.Sleep(100);
                }
            }



        }

        private static async Task GetAddrOnBlock(NBitcoin.RPC.RPCClient client, uint height, Dictionary<string, bool> addrs, List<string> lines)
        {
            var hash = await client.GetBlockHashAsync(height);
            var block = await client.GetBlockAsync(hash);
            foreach (var tx in block.Transactions)
            {
                foreach (var o in tx.Outputs)
                {
                    var pkey = o.ScriptPubKey;
                    var addr1 = pkey.GetDestinationAddress(network);

                    //var addr2 = pkey.PaymentScript.GetDestinationAddress(NBitcoin.Network.Main);
                    if (addr1 != null)
                    {
                        var outaddr = addr1.ToString();
                        if (addrs.ContainsKey(outaddr) == false)
                        {

                            addrs[outaddr] = true;
                            lines.Add(outaddr);
                        }
                    }

                }

            }

        }
    }
}
